package com.mikelouis.test.scala.impatient.chapter11_operators

import math.abs

/**
 * Created by yuazhu on 4/21/2015.
 */
object Chapter11 {

  // 3
  object Fraction {
    def apply(n: Int, d: Int) = new Fraction(n, d)

    def unapply(f: Fraction) = Some((f.num, f.den))
  }

  class Fraction(n: Int, d: Int) {
    val num = if (d == 0) 1 else n * sign(d) / abs(gcd(n, d))
    val den = if (d == 0) 0 else d * sign(d) / abs(gcd(n, d))

    def unary_- = new Fraction(-this.num, this.den)

    def unary_~ = new Fraction(this.den, this.num)

    def +(that: Fraction) = {
      val m = lcm(this.den, that.den)
      new Fraction(m / this.den * this.num + m / that.den * that.num, m)
    }

    def -(that: Fraction) = this + (-that)

    def *(that: Fraction) = new Fraction(this.num * that.num, this.den * that.den)

    def /(that: Fraction) = this * ~that

    override val toString = num + "/" + den
  }

  // 4
  class Money(val d: Int, val c: Int) extends Ordered[Money] {
    val total: Int = d * 100 + c

    def +(that: Money) = new Money(d + that.d, c + that.c)

    def -(that: Money) = new Money(d - that.d, c - that.c)

    override val toString = d + "$ " + c + "¢"

    override def compare(that: Money): Int = {
      this.total.compare(that.total)
    }

    //    override def equals(obj: scala.Any): Boolean = {
    //      if (obj.isInstanceOf[Money])
    //        this.total == obj.asInstanceOf[Money].total
    //      else
    //        false
    //    }
    override def equals(obj: scala.Any): Boolean = obj match {
      case x: Money => this.total == x.total
      case _ => false
    }
  }

  // 5
  object Table {
    def apply(html: String = "<table><tr>") = new Table(html)
  }

  class Table(val html: String = "<table><tr>") {
    override val toString = html + "</tr></table>"

    def |(col: String): Table = new Table(html + col.mkString("<td>", "", "</td>"))

    def ||(col: String): Table = new Table(html + col.mkString("</tr><tr><td>", "", "</td>"))
  }

  // 6
  object ASCIIArt {
    def apply(name: String) = new ASCIIArt(io.Source.fromFile(name + ".txt").mkString)
  }

  class ASCIIArt(fig: String) {
    override val toString = fig

    def *(that: ASCIIArt): ASCIIArt = {
      val linesA = this.toString.split("\n")
      val linesB = that.toString.split("\n")
      val linesCount = math.max(linesA.length, linesB.length)
      var buf = new String

      for (i <- 0 until linesCount) {
        if (i < linesA.length)
          buf += linesA(i) + "  "
        if (i < linesB.length)
          buf += linesB(i)
        buf += "\n"
      }

      new ASCIIArt(buf)
    }

    def +(that: ASCIIArt): ASCIIArt = new ASCIIArt(this.toString + "\n\n" + that.toString)
  }

  // 7
  class BigSequence {
    var b: Long = 0L

    override def toString: String = b.toBinaryString

    def apply(i: Int): Int = (((1L << i) & b) >> i).asInstanceOf[Int]

    def update(i: Int, value: Int) {
      if (value == 1)
        b |= (1L << i)
      else if (value == 0)
        b &= ~(1L << i)
    }
  }

  // 8
  class Matrix(val numOfRows: Int, val numOfCols: Int) {
    // var matrix: Array[Array[Int]] = init(numOfRows, numOfCols)
    val myMatrix = Array.ofDim[Int](numOfRows, numOfCols)

    def init(nRows: Int, nCols: Int) = Array.tabulate(nRows, nCols)((x, y) => 0)

    def +(that: Matrix): Unit = {
      if (this.numOfCols == that.numOfCols && this.numOfRows == that.numOfRows) {
        val newMatrix: Array[Array[Int]] = Array.ofDim[Int](numOfRows, numOfCols)
        for (i <- 0 until numOfRows) {
          for (j <- 0 until numOfCols) {
            newMatrix(i)(j) = myMatrix(i)(j) + that.myMatrix(i)(j)
          }
        }
      }
    }
  }

  // 10
  class RichFile(fileName: String) {
    override def toString: String = fileName
  }

  object RichFile {
    def unapply(f: RichFile): Option[(String, String, String)] = {
      val fn = f.toString
      val slash = fn.lastIndexOf("/")
      val dot = fn.lastIndexOf(".")
      Some((fn.substring(0, slash + 1), fn.substring(slash + 1, dot), fn.substring(dot + 1)))
    }
  }

  // 11
  object RichFile2 {
    // this split function has a problem, since / is at the first character, will introduce an extra empty string in the list
    // ex: /home/cay/readme.txt ==> List(, home, cay, readme.txt)
    def unapplySeq(f: String): Option[List[String]] = Some(f.split("/").toList)
  }

  def fileNameSplitUsingUnapplySeq(file: String): Unit = file match {
    case RichFile2(path@_*) =>
      println("UnapplySeq works")
      println(path)
    case _ => println("No match: ", file)

  }


  def main(args: Array[String]) {
    println(new Fraction(15, 10))
    println(new Fraction(-15, -10))

    println(new Money(1, 75))
    println(new Money(0, 50))
    println(new Money(1, 75) < new Money(0, 50))
    println((new Money(1, 75) + new Money(0, 50)) == new Money(2, 25))

    println(Table() | "Java" | "Scala" || "Gosling" | "Odersky" || "JVM" | "JVM, .NET")

    val RichFile(path, name, extension) = new RichFile("/home/cay/readme.txt")
    println(path, name, extension)

    fileNameSplitUsingUnapplySeq("/home/cay/readme.txt")

  }
}
